package crmdna.client.participant;

import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import crmdna.client.contactdetail.ContactDetail;
import crmdna.client.contactdetail.ContactDetail.ContactDetailProp;
import crmdna.client.member.Member;
import crmdna.client.member.MemberCore.MemberProp;
import crmdna.client.member.MemberEntity;
import crmdna.client.program.Program;
import crmdna.client.program.Program.ProgramProp;
import crmdna.client.sequence.Sequence;
import crmdna.client.sequence.Sequence.SequenceType;
import crmdna.client.user.User;
import crmdna.client.user.User.Action;
import crmdna.client.user.User.ResourceType;
import crmdna.client.user.UserCore;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.crmdna.Client;

public class Participant {	
	public static class ParticipantProp implements Comparable<ParticipantProp>{
		public long participantId;		
		public ContactDetailProp contactDetail = new ContactDetailProp();;
					
		public long programId;
		public long groupId;		
		public long memberId;
				
		@Override
		public int compareTo(ParticipantProp arg0) {
			return contactDetail.firstName.compareTo(arg0.contactDetail.firstName);			
		}		
	}
	
	static ParticipantProp create(String client, ContactDetailProp contact, boolean force, String login) {
		Client.ensureValidClient(client);
		
		throw new APIException(Status.ERROR_NOT_IMPLEMENTED, "not yet implemented");
	}
	
	static ParticipantProp createWithoutDuplicateCheck(String client, ContactDetailProp contact, 
			long programId, String login) {
		Client.ensureValidClient(client);
		
		ContactDetail.ensureValid(client, contact);
		
		ProgramProp programProp = Program.safeGet(client, programId).toProp();
		User.ensureAccess(client, login, ResourceType.PROGRAM, 
				programProp.groupProp.groupId, Action.WRITE);
		
		//check if there is a close enough match already in participants entity
		// if true then 
		//  if (not force)
		//       throw error
		//  else
		//       add
		
		List<ContactDetailProp> contacts = new ArrayList<ContactDetailProp>();
		contacts.add(contact);
		
		
		
		return null;
	}
	
	public static ParticipantEntity safeGet(String client, long participantId) {
        
        Client.ensureValidClient(client);
        
        ParticipantEntity entity = ofy(client).load().type(ParticipantEntity.class).id(participantId).get();
        if (null == entity)
                throw new APIException(Status.ERROR_RESOURCE_DOES_NOT_EXIST, 
                                "Group [" + participantId + "] does not exist");
        return entity;          
}  
	
	static List<ParticipantProp> getQSMatches(String client, String searchString, List<Long> programIds, 
			String login) {
		Client.ensureValidClient(client);		
		UserCore.ensureValidUser(client, login);
		
		return null;					
	}
	
	static ParticipantProp updateContactDetails(String client, int participantId, 
			String newFirstName, String newLastName, String newHomePhoneNo, String newOfficePhoneNo,
			String newMobilePhoneNo, String newEmail) {
		return null;
	}
	
	static List<MemberEntity> getMatching(String client, List<ContactDetailProp> contactDetailProps) {
				
		for (ContactDetailProp contactDetailProp : contactDetailProps) {			
			if (contactDetailProp.email != null)
				contactDetailProp.email = contactDetailProp.email.toUpperCase();
		}	
				
		Map<String, Set<Integer>> emailMap = new HashMap<>();				
		Map<String, Set<Integer>> mobileMap = new HashMap<>();
				
		for (int index = 0; index < contactDetailProps.size(); index++) {
			ContactDetailProp c = contactDetailProps.get(index);
			if (c.email != null) {
				if (! emailMap.containsKey(c.email)) 
					emailMap.put(c.email, new HashSet<Integer>());
				
				Set<Integer> existing = emailMap.get(c.email);
				existing.add(index);				
			} else if (c.mobilePhone != null) {
				if (! mobileMap.containsKey(c.mobilePhone))
					mobileMap.put(c.mobilePhone, new HashSet<Integer>());
				
				Set<Integer> existing = mobileMap.get(c.mobilePhone);
				existing.add(index);
			} 
		}
		
		List<MemberEntity> memberEntities = new ArrayList<>();
		//initialize to nulls
		for (int index = 0; index < contactDetailProps.size(); index++) {
			memberEntities.add(null);
		}
					
		List<MemberEntity> emailMatches = ofy(client).load().type(MemberEntity.class)
				.filter("email in", emailMap.keySet()).list();
		
		List<MemberEntity> mobileMatches = ofy(client).load().type(MemberEntity.class)
				.filter("mobilePhone in", mobileMap.keySet()).list();
		
		for (MemberEntity me : emailMatches) {
			MemberProp mp = me.toProp();
			String memberName = mp.contact.firstName + " " + mp.contact.lastName;
			
			for (Integer index : emailMap.get(mp.contact.email)) {
				String particantName = contactDetailProps.get(index).firstName 
						+ contactDetailProps.get(index).lastName;
				
				if (Utils.closeEnough(memberName, particantName))
					memberEntities.set(index, me);
			}
		}
		
		for (MemberEntity me : mobileMatches) {
			MemberProp mp = me.toProp();
			String memberName = mp.contact.firstName + " " + mp.contact.lastName;
			
			for (Integer index : mobileMap.get(mp.contact.mobilePhone)) {
				if (memberEntities.get(index) == null) {
					String particantName = contactDetailProps.get(index).firstName 
						+ contactDetailProps.get(index).lastName;
					
					if (Utils.closeEnough(memberName, particantName))
						memberEntities.set(index, me);
				}
			}
		}
		
		return memberEntities;
	}
		
	public static void bulkUpload(String client, List<ContactDetailProp> contactDetailProps, 
			long programId, boolean force, String login) {
		
		//This function will save the entered details as is into participant entities. If the member
		// is new it will create member entities. 
		
		//logic is as follows -
		
		//for existing members
		//   1. add program to memberentity
		//   2. populate member id to participantentity
		//for new members
		//   1. save as members first and get the member id
		//   2. populate member id to participant entity
		//save all participant entities
		
		//TODO
		//check if there is a close enough match already in participants entity
				// if true then 
				//  if (not force)
				//       throw error
				//  else
				//       add
		
		
		Client.ensureValidClient(client);
		
		ProgramProp programProp = Program.safeGet(client, programId).toProp();
		User.ensureAccess(client, login, ResourceType.PROGRAM, 
				programProp.groupProp.groupId, Action.WRITE);
				
		for (ContactDetailProp contactDetailProp : contactDetailProps) {
			if (contactDetailProp.centerId == 0)
				contactDetailProp.centerId = programProp.groupProp.groupId;			
		}				
		
		ContactDetail.ensureValid(client, contactDetailProps);				
		
		List<MemberEntity> memberEntities = getMatching(client, contactDetailProps);
					
		List<ParticipantEntity> participantEntities = 
				getParticipantEntities(client, contactDetailProps, programId);
		
		List<ContactDetailProp> toAdd = new ArrayList<>();
		for (int index = 0; index < contactDetailProps.size(); index++) {
			toAdd.add(null);
		}
		
		for (int index = 0; index < contactDetailProps.size(); index++) {
			ContactDetailProp c = contactDetailProps.get(index);
			ParticipantEntity pe = participantEntities.get(index);
			MemberEntity me = memberEntities.get(index);
			
			if (me == null) {
				toAdd.add(c);
			} else {
				pe.memberId = me.toProp().memberId;	
			}
		}
		
		List<Long> memberIds = Member.bulkCreateWithoutValidations(client, 
				toAdd, programId);
		
		for (int index = 0; index < memberIds.size(); index++) {
			Long memberId = memberIds.get(index);
			if (memberId != null) {
				ParticipantEntity pe = participantEntities.get(index);
				pe.memberId = memberId;
			}				
		}
		
		//add program to existing members
		Member.addProgramToMembers(client, memberEntities, programId);
		
		//save all participants entity
		ofy(client).save().entities(participantEntities);																							
	}
	
	

	private static List<ParticipantEntity> getParticipantEntities(String client, List<ContactDetailProp> contactDetailProps, 
			long programId) {
		//returns list of ids. first id will correspond to first element in contactDetailProps and so on
		ContactDetail.ensureValid(client, contactDetailProps);
		
		List<ParticipantEntity> entities = new ArrayList<>();
		
		List<Long> ids = Sequence.getNext(client, SequenceType.PARTICIPANT, contactDetailProps.size());
		for (int i = 0; i < contactDetailProps.size(); i++) {
			ContactDetailProp c = contactDetailProps.get(i);
			ParticipantEntity pe = new ParticipantEntity();
			pe.participantId = ids.get(i);
			
			pe.email = c.email;
			pe.firstName = c.firstName;
			pe.lastName = c.lastName;
			pe.gender = c.gender;
			pe.groupId = c.centerId;
			pe.homeAddress = c.homeAddress;
			pe.homePhone = c.homePhone;
			pe.lastName = c.lastName;
			pe.officeAddress = c.officeAddress;
			pe.officePhone = c.officePhone;
			pe.programId = programId;
			
			//TODO - populate quick search tags
			
			entities.add(pe);
		}
				
		return entities;
	}				
}
